#include <zsystem/core/lua/luaPacket.h>
#include <zsystem/core/common/logdump.h>
#include <zsystem/core/common/md5.h>
#include <zsystem/core/networking/connection.h>

int traceback (lua_State *L)
{
	if (!lua_isstring(L, 1))  /* 'message' not a string? */
		return 1;  /* keep it intact */
	lua_getfield(L, LUA_GLOBALSINDEX, "debug");
	if (!lua_istable(L, -1)) {
		lua_pop(L, 1);
		return 1;
	}
	lua_getfield(L, -1, "traceback");
	if (!lua_isfunction(L, -1)) {
		lua_pop(L, 2);
		return 1;
	}
	lua_pushvalue(L, 1);  /* pass error message */
	lua_pushinteger(L, 2);  /* skip this function and traceback */
	lua_call(L, 2, 1);  /* call debug.traceback */
	return 1;
}

//////////////////////////////////////////////////////////////////////////////////////
int LUA_dumplog(lua_State* L)
{
  if (lua_gettop(L) < 1) 
  {
		lua_pushboolean(L, 0);
		return 1;
	}
  const char* message = lua_tostring(L, 1);
  Z_DUMP_LOG("[lua] " << message);
  google::FlushLogFiles(INFO);
	return 0;
}

int LUA_warnlog(lua_State* L)
{
  if (lua_gettop(L) < 1) 
  {
		lua_pushboolean(L, 0);
		return 1;
	}
  const char* message = lua_tostring(L, 1);
  Z_DUMP("[lua] " << std::yellow << message);
  LOG(WARNING) << "[lua] " << std::yellow << message;
  google::FlushLogFiles(INFO);
	return 0;
}

int LUA_md5(lua_State* L)
{
  if (lua_gettop(L) < 1) 
  {
		lua_pushboolean(L, 0);
		return 1;
	}
  const char* data = lua_tostring(L, 1);
  Z_I32 len = strnlen(data, 1024);
  Z_U8* buffer = (Z_U8*)Z_MALLOC(len);
  memcpy(buffer, data, len);

  zsystem::md5 md5obj;
	char result[33] = {0};
	md5obj.update(buffer, len);
	md5obj.getResultString(result);
  Z_FREE(buffer);

  Z_DUMP("[lua] md5(" << data << ") = " << result);
  lua_pushboolean(L, 1);
  lua_pushlstring(L, result, 32);
	return 2;
}

const struct luaL_Reg lua_common_lib[] = {
		{"dumplog", LUA_dumplog},
		{"warnlog", LUA_warnlog},
		{"md5", LUA_md5},
		{NULL, NULL}
};

//////////////////////////////////////////////////////////////////////////////////////
zsystem::luaPacket::luaPacket()
: _lua(Z_NULL)
{
}

zsystem::luaPacket::~luaPacket()
{
  if ( _lua != Z_NULL )
  {
    lua_close(_lua);
    _lua = Z_NULL;
  }
}

const Z_BOOL 
zsystem::luaPacket::init(const char* scriptFoldPath, const luaL_Reg *networkingReg, const luaL_Reg *logicalReg)
{
  Z_ASSERT_RETURN( _lua == Z_NULL, Z_FALSE );
  _lua = luaL_newstate();
  Z_ASSERT_RETURN( _lua != Z_NULL, Z_FALSE );
  luaL_openlibs(_lua);
  // Todo: register common functions
  luaL_register(_lua, "common", lua_common_lib);
  if ( networkingReg != Z_NULL )
    luaL_register(_lua, "networking", networkingReg);
  if ( logicalReg != Z_NULL )
    luaL_register(_lua, "logical", logicalReg);

  // Load main.lua
	int base = lua_gettop(_lua);
	lua_pushcfunction(_lua, traceback);
  char filePath[1024] = {0};
  Z_SNPRINTF(filePath, sizeof(filePath), "%s%smain.lua", scriptFoldPath, Z_PATH_SLASH);
  int status = luaL_loadfile(_lua, filePath);
  if ( status != 0)
  {
    LOG(WARNING) << "load lua failed! file: " << filePath << ", error message: " << luaL_checkstring(_lua, -1);
    Z_DUMP(std::yellow << "lua error: " << luaL_checkstring(_lua, -1));
    lua_pop(_lua, 2);
    return Z_FALSE;
  }
  status = lua_pcall(_lua, 0, LUA_MULTRET, base + 1);
  if ( status != 0 )
  {
    LOG(WARNING) << "lua init failed! file: " << filePath << ", error message: " << luaL_checkstring(_lua, -1);
    Z_DUMP(std::yellow << "lua error: " << luaL_checkstring(_lua, -1));
    lua_remove(_lua, base+1);
    return Z_FALSE;
  }
  lua_settop(_lua, base);
  return Z_TRUE;
}

const Z_BOOL 
zsystem::luaPacket::call(const char* funcName, const char* json, const Z_I32 len, char* resJson, Z_I32* resJsonLen)
{
  Z_ASSERT_RETURN(_lua != Z_NULL, Z_FALSE);
	int base = lua_gettop(_lua);
	Z_ASSERT_RETURN(findLuaGlobalFunction(funcName), Z_FALSE);

  int paramNum = 0;
  if ( json != Z_NULL || len >= 0 )
  {
    lua_pushstring(_lua, json);
    paramNum = 1;
  }

	if (callLuaScript(paramNum) != 2) 
  {
    LOG(WARNING) << "zsystem::luaPacket::call callLuaScript failed!";
    lua_settop(_lua, base);
	}

  int luaOK = lua_toboolean(_lua, -2);
  if ( luaOK )
  {
    if ( resJsonLen != Z_NULL)
    {
      size_t resJsonLenTmp;
      const char* resJsonTmp = lua_tolstring(_lua, -1, &resJsonLenTmp);
      if ( resJsonLenTmp > 0 )
      {
        if ( resJsonLenTmp < zsystem::packet::MAX_PACKET_SIZE )
        {
          *resJsonLen = resJsonLenTmp;
          resJson = (char *)Z_MALLOC(*resJsonLen);
          if ( resJson != Z_NULL )
          {
            memcpy(resJson, resJsonTmp, *resJsonLen);
          }
          else
          {
            Z_DUMP_WARN("luaPacket::call() Z_MALLOC failed");
            resJson = Z_NULL;
            luaOK = 0;
          }
        }
        else
        {
          Z_DUMP_WARN("luaPacket::call() result len: " << resJsonLenTmp << ", Max length is 65535");
          resJson = Z_NULL;
          luaOK = 0;
        }
      }
      else
      {
        resJson = Z_NULL;
      }
    }
  }
  lua_settop(_lua, base);
  return luaOK == 1;
}

const Z_BOOL 
zsystem::luaPacket::call(const char* funcName, zsystem::RJsonValue& value, char* resJson, Z_I32* resJsonLen)
{
  zsystem::RJsonStringBuffer s(0, 65535);
  zsystem::RJsonStringWrite writer(s);
  value.Accept(writer);
  return call(funcName, s.GetString(), s.GetSize(), resJson, resJsonLen);
}


const int 
zsystem::luaPacket::callLuaScript(int args)
{
	if (args < 0) args = 0;
	int base = lua_gettop(_lua) - 1 - args;
	if (base < 0) return 0;
  Z_DUMP_LOG("callLuaScript base: " << base);

	lua_pushcfunction(_lua, traceback);
	lua_insert(_lua, base+1);

	if (lua_pcall(_lua, args, LUA_MULTRET, base+1) != 0) {
		LOG(WARNING) << "Call lua failed! " << lua_tostring(_lua, -1);
    Z_DUMP(std::yellow << "lua error: " << luaL_checkstring(_lua, -1));
    lua_remove(_lua, base+1);
		return 0;
	}
	else {
    lua_remove(_lua, base+1);
		int ret = lua_gettop(_lua) - base;
		return ret < 0 ? 0 : ret;
	}
}

const int 
zsystem::luaPacket::findLuaGlobalFunction(const char* func)
{
	lua_getglobal(_lua, func);
	if (lua_isfunction(_lua, -1)) {
		return 1;
	}
	else {
		lua_pop(_lua, 1);
		return 0;
	}
}
